package com.cj.bigfileupload.service.impl;

import com.cj.bigfileupload.common.GlobalConsts;
import com.cj.bigfileupload.common.WebUploadConfigParam;
import com.cj.bigfileupload.common.util.FileUtil;
import com.cj.bigfileupload.controller.request.MultipartFileParam;
import com.cj.bigfileupload.service.IWebUploadService;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Created by c14059 on 2017/10/31.
 */
@Service
public class WebUploadServiceImpl implements IWebUploadService {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebUploadServiceImpl.class);

    @Resource
    private WebUploadConfigParam webUploadConfigParam;

    @Override
    public boolean uploadFileByMappedByteBuffer(String uploadDirPath, MultipartFileParam param) throws IOException {
        String fileName = param.getName();
        String tempFileName = fileName + GlobalConsts.FILE_TMP_SUFFIX;
        File tmpDir = new File(uploadDirPath);
        File tmpFile = new File(uploadDirPath, tempFileName);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
        FileChannel fileChannel = null;
        try {
            fileChannel = tempRaf.getChannel();
            // 写入该分片数据
            long offset = webUploadConfigParam.getChunkSize() * param.getChunk();
            byte[] fileData = param.getFile().getBytes();
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
            mappedByteBuffer.put(fileData);
            // 释放
            FileUtil.freedMappedByteBuffer(mappedByteBuffer);
        } finally {
            if (tempRaf != null) {
                tempRaf.close();
            }
            if (fileChannel != null) {
                fileChannel.close();
            }
        }
        boolean isOk = checkAndSetUploadProgress(param, uploadDirPath);
        if (isOk) {
            // 修改文件名称
            boolean flag = renameFile(tmpFile, fileName);
            LOGGER.debug("upload complete !!" + flag + " name=" + fileName);
            return true;
        }
        return false;
    }

    /**
     * 检查并修改文件上传进度
     *
     * @param param
     * @param uploadDirPath
     * @return
     * @throws IOException
     */
    private boolean checkAndSetUploadProgress(MultipartFileParam param, String uploadDirPath) throws IOException {
        String fileName = param.getName();
        File confFile = new File(uploadDirPath, fileName + GlobalConsts.FILE_CONF_SUFFIX);
        RandomAccessFile accessConfFile = null;
        byte isComplete = Byte.MAX_VALUE;
        try {
            accessConfFile = new RandomAccessFile(confFile, "rw");
            // 把该分段标记为 true 表示完成
            LOGGER.debug("set part " + param.getChunk() + " complete");
            accessConfFile.setLength(param.getChunks());
            accessConfFile.seek(param.getChunk());
            accessConfFile.write(Byte.MAX_VALUE);

            // completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
            byte[] completeList = FileUtils.readFileToByteArray(confFile);
            for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
                // 与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
                isComplete = (byte) (isComplete & completeList[i]);
            }
        } finally {
            if (accessConfFile != null) {
                accessConfFile.close();
            }
        }

        if (isComplete == Byte.MAX_VALUE) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 文件重命名
     *
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    private boolean renameFile(File toBeRenamed, String toFileNewName) {
        // 检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            LOGGER.info("File does not exist: " + toBeRenamed.getName());
            return false;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        // 将要改名的文件已存在，则将老文件删除，用新文件覆盖
        if (newFile.exists()) {
            newFile.delete();
        }
        // 修改文件名
        return toBeRenamed.renameTo(newFile);
    }

    @Override
    public boolean uploadFileRandomAccessFile(String uploadDirPath, MultipartFileParam param) throws IOException {
        String fileName = param.getName();
        String tempFileName = fileName + GlobalConsts.FILE_TMP_SUFFIX;
        File tmpDir = new File(uploadDirPath);
        File tmpFile = new File(uploadDirPath, tempFileName);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile accessTmpFile = null;
        try {
            accessTmpFile = new RandomAccessFile(tmpFile, "rw");
            long offset = webUploadConfigParam.getChunkSize() * param.getChunk();
            //定位到该分片的偏移量
            accessTmpFile.seek(offset);
            //写入该分片数据
            accessTmpFile.write(param.getFile().getBytes());
        } finally {
            if (accessTmpFile != null) {
                // 释放
                accessTmpFile.close();
            }
        }

        boolean isOk = checkAndSetUploadProgress(param, uploadDirPath);
        if (isOk) {
            boolean flag = renameFile(tmpFile, fileName);
            LOGGER.debug("upload complete !!" + flag + " name=" + fileName);
            return true;
        }
        return false;
    }
}
